답할 수 있어야 하는 것들
- 운영체제에서 제공하는 서비스
- 시스템 콜을 사용하는 방법
- 모놀리식, 계층화, 마이크로 커널, 모듈 및 하이브리드 전략 비교 / 대조
- 운영체제 부팅 프로세스
- 운영체제 성능 모니터링 도구
운영체제 서비스
사용자에게 도움을 주는
- 사용자 인터페이스
- UI
- GUI
- 터치 스크린 인터페이스
- CLI (명령어 라인 인터페이스)
- UI
- 프로그램 수행 : 시스템은 프로그램을 메모리에 적재해 실행하 ㄹ수 있고, 끝낼 수 있어야 한다.
- 입출력 연산 : 수행되는 프로그램은 입출력을 요구할 수 있다. 입출력 수행의 수단을 제공해야 한다.
- 파일 시스템 조작 : 개인의 선택 / 특정 특성 / 성능을 제공하기 위해 다양한 파일 시스템을 제공해야 한다.
- 통신 : 한 프로세스가 다른 프로세스와 정보를 교환해야 할 필요가 있다. 통신은 공유 메모리 / 메시지 전달 기법을 사용하여 구현될 수 있다.
- 오류 탐지 : 모든 가능한 오류를 항상 의식하고 있어야 한다. 올바르고 일관성 있는 계산을 보장하기 위해 각 유형의 오류에 대해 적당한 조처를 해야 한다.
시스템 자체의 효율적인 동작을 보장하기 위한
- 자원 할당 : 다수의 프로세스나 다수의 작업이 동시에 실행 될 때, 각각에 자원을 할당해 주어야 한다.
- 기록 작성 (logging) : 어떤 프로그램이 어떤 종류의 컴퓨터 자원을 얼마나 많이 사용하는지를 추적할 수 있다.
- 보호 (protection) 와 보안 (security) : 보호는 시스템 자원에 대한 모든 접근이 통제되도록 보장하는 것을 필요로 한다. 보안은 외부 입출력 장치들을 부적합한 접근 시도로부터 지키고, 침입의 탐지를 위해 모든 접속을 기록한다.
사용자와 운영체제 인터페이스
사용자가 운영체제와 접촉하는 방식
- 명령 인터프리터를 통해 운영체제와 접촉하는 것
- 그래픽 기반 사용자 인터페이스를 통해 운영체제와 접촉하는 것
명령 인터프리터
가장 중요한 기능은 사용자가 지정한 명령을 가져와서 수행하는 것이다.
명령어들이 구현되는 방식은 두 가지 일반적인 방법이 있다.
- 명령 인터프리터 자체가 명령을 실행할 코드를 가지는 경우 : 제공될 수 있는 명령의 수가 명령 인터프리터의 크기를 결정한다.
- 시스템 프로그램에 의해 대부분의 명령을 구현하는 경우 : 명령 인터프리터는 전혀 명령을 알지 못하고, 단지 메모리에 적재되어 실행될 파일을 식별하기 위해 명령을 사용한다.
- 프로그래머가 적합한 프로그램 로직을 가진 새로운 파일을 생성하여 새로운 명령을 쉽게 추가할 수 있다.
- 명령 인터프리터 프로그램이 아주 작아질 수 있고, 명령을 추가하기 위해 변경될 필요가 없다.
그래픽 기반 사용자 인터페이스
사용자가 직접 명령어를 입력하는 것이 아닌, 데스크톱이라고 특정지어지는 마우스를 기반으로 한 윈도 메뉴 시스템을 사용한다.
터치스크린 인터페이스
터치스크린에서 손가락을 누르거나 스와이프 하는 등의 제스처로 상호 작용한다.
시스템 콜
운영체제에 의해 사용 가능하게 된 서비스에 대한 인터페이스를 제공한다.
이러한 호출은 일반적으로 C와 C++ 언어로 작성된 함수 형태로 제공된다.
응용 프로그래밍 인터페이스
대부분의 응용 개발자들은 응용 프로그래밍 인터페이스 (API) 에 따라 프로그램을 설계한다.
API는 각 함수에 전달되어야 할 매개변수들과 프로그래머가 기대할 수 있는 반환 값을 포함하여 응용 프로그래머가 사용 가능한 함수의 집합을 명시한다.
응용 프로그래머가 실제 시스템 콜을 부르는 것보다 API에 따라 프로그래밍하는 것은 선호하는 이유
먼저 프로그램 호환성과 관련이 있다.
API 를 사용하는 프로그래머는 자신의 프로그램이 같은 API를 지원하는 어느 시스템에서건 컴파일 되고 실행된다는 것을 기대할 수 있다.
또한 실제 시스템 콜은 좀 더 자세한 명세가 필요하고 프로그램 상에서 작업하기가 API보다 더 어렵다.
그럼에도 API를 호출하는 것과 시스템 콜을 호출하는 것은 강한 상관 관계가 존재한다.
대부분의 POSIX, Windows API 는 운영체제가 제공하는 고유의 시스템 콜과 유사하다.
실행 시간 환경 (RunTimeEnvironment, RTE)
컴파일러 또는 인터프리터를 포함한 특정 프로그래밍 언어로 작성된 응용 프로그램을 실행하는 데 필요한 전체 소프트 웨어 제품군과 라이브러리 또는 로더와 같은 다른 소프트웨어이다.
운영체제가 제공하는 시스템 콜에 대한 연결고리 역할을 하는 시스템 콜 인터페이스
를 제공한다.
API 함수의 호출을 가로채어 필요한 운영체제 시스템 콜을 부른다.
호출자는 시스템 콜의 구현과 실행 중 어떤 작업을 하는지 알 필요가 없다.
단지 API를 준수하고 결과로 운영체제가 어떤 행동을 할 것인지만 이해하면 된다.
시스템 콜은 사용되는 컴퓨터에 따라 다른 방법으로 발생한다.
필요한 정보의 유형과 양은 특정 운영체제와 호출에 따라 다양하다.
운영체제에 매개변수를 전달하기 위한 방법
- 매개변수를 레지스터 내에 전달하는 것
- 레지스터보다 더 많은 매개변수가 존재할 수도 있다 -> 매개변수를 테이블, 블록에 저장하기
- 매개변수가 메모리 내의 블록이나 테이블에 저장되고, 블록의 주소가 레지스터 내에 매개변수로 전달되는 것
- 리눅스에서는 5개 이하의 매개변수가 있으면 레지스터가 사용되고, 매개변수 개수가 5개를 넘으면 블록 방법을 사용한다.
- 매개변수는 프로그램에 의해 스택에 넣어질 수 있고, 운영체제에 의해 꺼내진다.
시스템 콜의 유형
- 프로세스 제어
- 파일 조작
- 장치 조작
- 정보 유지 보수
- 통신 / 보호
프로세스 제어
실행 중인 프로그램은 수행을 정상적으로 end()
/ 비정상적으로 abort()
멈출 수 있어야 한다.
정상이거나 비정상인 상황에서, 운영체제는 명령 인터프리터로 제어를 전달해야 한다.
한 프로그램을 실행하고 있는 프로세스가 다른 프로그램을 적재 load()
하고 실행 execute()
하기를 원할 수도 있다.
해당 기능은 명령 인터프리터가 사용자 명령 또는 마우스 클릭을 통해 지시된 프로그램을 실행하는 것을 허용한다.
그렇다면 적재된 프로그램이 종료되었을 때 어디로 제어를 되돌려 줄까?
기존 프로그램이 유실될지, 보관될지, 새로운 프로그램과 병행하게 실행을 계속하도록 허용할 것인지 하는 문제와 관련이 있다.
새로운 프로그램이 종료될 때 제어가 기존 프로그램으로 돌아간다면, 반드시 기존 프로그램의 메모리 이미지를 보관해야 한다.
즉, 실질적으로 한 프로그램이 다른 프로그램을 호출하는 기법을 만든 셈이다. 이를 위한 시스템 콜이 존재한다. (create_process()
)
새로운 잡(job)이나 프로세스, 혹은 잡들이나 프로세스들의 집합을 생성한다면, 실행을 제어할 수 있어야 한다.
잡의 우선순위, 최대 허용 실행 시간 등을 포함하여 잡 혹은 프로세스의 속성들을 결정하고 재설정 (reset) 할 수 있는 능력이 필요하다. (get_process_attributes()
, set_process_attributes()
)
또한 새로 생성한 잡이나 프로세스가 잘못되었거나 더 이상 필요 없다면 종료하기를 원할 수 있다. (terminate_process()
)
새로운 프로세스를 생성한 후에는, 이들의 실행이 끝나기를 기다려야 할 필요가 있을 수 있다.
wait_time()
: 일정 시간만큼 기다리기wait_event()
: 특정 이벤트가 일어날 때까지 기다리기signal_event()
: 프로세스들은 이벤트가 일어나면 신호를 보내야 한다.
둘 이상의 프로세스들은 빈번하게 데이터를 공유하기 때문에, 공유 데이터를 잠글 수 있는 시스템 콜을 제공한다.
그러면 잠금이 해제될 때까지 어느 프로세스도 데이터에 접근할 수 없게 된다.
acquire_lock()
, release_lock()
시스템 콜을 제공한다.
단일 태스킹 시스템 - Arduino
표준 Arduino 플랫폼은 운영체제를 제공하지 않고, 부트 로더라 불리는 작은 소프트웨어가 프로그램을 Arduino의 특정 영역으로 적재한다.
한순간에 하나의 프로그램만 메모리에 존재할 수 있으므로, 단일 태스킹 시스템으로 간주된다. 즉, 다른 프로그램이 적재되면 기존 프로그램을 대체하게 된다.
다중 태스킹 시스템 -FreeBSD
사용자가 시스템에 로그인할 때 사용자가 선택한 셸이 수행되어 명령을 기다렸다가 사용자가 요청한 프로그램으 수행한다.
새로운 프로세스를 시작하기 위해 셸은 fork()
시스템 콜을 실행한다.
선택된 프로그램이 exec()
시스템 콜을 통해 메모리에 적재되고, 프로그램이 수행된다.
셸은 프로세스가 종료하기를 기다리거나 '백그라운드' 에서 프로세스를 수행한다.
백그라운드에서 프로세스를 수행하는 경우, 셸은 바로 다음 명령이 입력되기를 기다린다.
백그라운드에서 프로세스가 수행될 때, 프로세스는 셸이 사용하고 있는 자원인 키보드로부터 직접 입력을 받을 수 없다.
그러나 사용자는 셸에게 입력을 제외한 다른 요청은 자유롭게 할 수 있다.
프로세스가 종료되면 exit()
시스템 콜을 수행하며, 0이나 오류 코드를 돌려준다.
파일 관리
파일을 생성 create()
하고 삭제 delete()
할 수 있어야 한다. 파일의 이름이나 파일 속성의 일부가 필요하다.
파일이 생성되면 그것을 열고 open()
사용해야 한다.
또한 읽고 read()
, 쓰고 write()
, 위치 변경 reposition()
(되감기 rewind()
나 파일 끝으로 건너뛰기) 할 수 있다.
마지막으로 파일을 더 이상 사용하지 않음을 나타내는 파일 닫기 close()
가 필요하다.
파일이나 디렉터리에 대해 여러 속성의 값을 결정할 수 있어야 하고, 재설정 reset()
할 수 있어야 한다.
파일 속성은 파일 이름, 파일 유형, 보호 코드, 회계 정보 등을 포함한다.
이를 위해서는 최소한 파일 속성 획득 get_file_attribute()
과 파일 속성 설정 set_file_attribute()
의 두 시스템 콜이 필요하다.
몇몇 운영체제는 파일 이동 move()
와 복사 copy()
같은 더 많은 시스템 콜을 제공한다.
장치 관리
프로세스는 작업을 계속 수행하기 위해 추가 자원(주 기억장치, 디스크 드라이브, 파일에의 접근)이 필요할 수 있다.
자원들을 사용할 수 있다면, 자원이 주어지고 제어가 사용자 프로그램으로 복귀될 수 있다.
그렇지 않으면, 프로그램은 충분한 자원이 사용 가능하게 될 때까지 기다려야 한다.
다수의 사용자가 동시에 사용하는 시스템은 독점적인 장치 사용을 보장받기 위해 장치를 요청 request()
하는 것을 요구한다.
장치의 사용이 끝나면 반드시 방출 release()
해야 한다.
이런 기능은 파일의 열기, 닫기 시스템 콜과 비슷하다.
다른 운영체제들은 장치에 대해 통제되지 않은 접근을 허용하기도 한다.
일단 장치를 요청하고 할당받게 되면, 파일과 마찬가지로 read()
, write()
, reposition()
할 수 있다.
입출력 장치와 파일 간에는 유사성이 매우 많기 때문에, 많은 운영체제가 이 둘을 통합된 파일 - 장치 구조로 결합하였다.
정보 유지 관리
많은 시스템이 메모리를 dump()
하기 위한 시스템 콜을 제공한다. 이는 디버깅에 유용하다.
많은 운영체제는 프로그램의 time profㅌ을 제공한다.
time profile은 프로그램이 특정 위치, 혹은 위치의 집합에서 수행한 시간의 양을 나타낸다.
운영체제는 현재 운영되고 있는 모든 프로세스에 관한 정보를 가지고 있으며, 이런 정보에 접근하기 위한 시스템 콜도 있다.
일반적으로, 그 프로세스 정보를 획득하고 설정하기 위한 get_process_attributes()
와 set_process_attributes()
가 있다.
통신
통신 모델에는 메시지 전달과 공유 메모리의 두 가지 일반적인 모델이 있다.
- 메시지 전달 모델
통신하는 두 프로세스가 정보를 교환하기 위해 서로 메시지를 주고 받는다.
메시지는 두 프로세스 사이에 직접 교환되거나 우편함을 통해 간접적으로 교환될 수 있다.
통신이 이루어지기 전에 연결이 반드시 열려야 한다.
상대 통신자가 동일한 CPU에 있는 프로세스이든지 통신 네트워크에 의해 연결된 다른 컴퓨터에 있는 프로세스이든지 간에 그 이름을 반드시 알고 있어야 한다.
네트워크의 각 컴퓨터는 호스트 이름을 가지며, 각 컴퓨터는 이들 이름으로 일반적으로 알려져 있다.
각 프로세스는 프로세스 이름을 가지고 있으며, 이 이름은 운영체제에 의해 동등한 식별자로 변환되고, 식별자는 운영체제가 그 프로세스를 가리키는 데 사용할 수 있다.
호스트 이름과 프로세스 이름은 각각 get_hostid()
와 get_processid()
시스템 콜이 변환을 수행한다.
이 식별자는 시스템의 통신 모델에 따라 파일 시스템의 open, close 호출에 전달되거나, 특정 open_connection()
과 close_connection()
시스템 콜에 전달된다.
수신 프로세스는 통상 통신이 일어날 수 있도록 accept_connection()
호출에 permission을 제공한다.
연결을 받아들일 프로세스들의 대부분은 특수 목적의 daemon 이다.
wait_for_connection()
호출을 수행하고, 연결이 이루어질 때 깨어난다.
클라이언트로 알려진 통신의 출발지와 서버로 알려진 수신 daemon은 read_message()
와 write_message()
시스템 콜에 의해 메시지들을 교환한다.
close_connection()
호출은 통신을 종료한다.
- 공유 메모리 모델
프로세스는 다른 프로세스가 소유한 메모리 영역에 대한 접근을 위해 shared_memory_create()
와 shared_memory_attach()
시스템 콜을 사용한다.
이때 프로세스는 동일한 위치에 동시에 쓰지 않도록 보장할 책임을 진다.
또한 프로세스 모델의 변형으로 디폴트로 메모리를 공유하는 스레드도 있다.
두 방법은 운영체제에서 보편적이며, 시스템 대부분은 둘 다 구현한다.
메시지 전달은 피해야 할 충돌이 없기 때문에 소량의 데이터 교환 시에 유용하다. 또한 컴퓨터 간의 통신을 위해 메모리 공유보다 구현하기 쉽다.
공유 메모리는 한 컴퓨터 안에서는 메모리 전송 속도로 수행할 수 있으므로, 최대 속도와 편리한 통신을 허용한다.
그러나 보호와 동기화 부분에서 여러 문제점을 가지고 있다.
보호
컴퓨터 시스템이 제공하는 자원에 대한 접근을 제어하기 위한 기법을 지원한다.
set_permision()
과get_permission()
: 파일, 디스크와 같은 자원의 허가 권한을 설정하는 데 이용된다.allow_user()
와deny_user()
: 특정 사용자가 지정된 자원에 대해 접근이 허가 / 불허되었는지를 명시한다.
시스템 서비스
시스템 서비스는 시스템 유틸리티로도 알려진, 프로그램 개발과 실행을 위해 더 편리한 환경을 제공한다.
몇몇은 단순히 시스템 콜에 대한 사용자 인터페이스이며, 나머지는 훨씬 더 복잡하다.
다음과 같은 범주로 분류할 수 있다.
- 파일 관리 : 파일과 디렉터리를 생성 / 삭제 / 복사 / 이름 변경 / 인쇄 / 열거 / 조작 한다.
- 상태 정보
- 파일 변경 : 디스크나 다른 저장 장치에 저장된 파일의 내용을 생성하고 변경하기 위해 text editor를 사용할 수 있다.
- 프로그래밍 언어 지원
- 프로그램 적재와 수행 : 프로그램이 어셈블되거나 컴파일된 후, 수행되려면 반드시 메모리에 적재(load) 되어야 한다. absolute loader, relocatable loader, linkage editor, overlay loader 등을 제공할 수 있고, 고급어나 기계어를 위한 디버깅 시스템도 필요하다.
- 통신 : 프로세스, 사용자, 다른 컴퓨터 시스템들 사이에 가상 접속을 이루기 위한 기법을 제공한다.
- 백그라운드 시스템 : 부트할 때 특정 시스템 프로그램을 시작시킬 수 있는 방법을 갖는다. 항상 실행되는 시스템 프로그램 프로세서는 서비스, 서브시스템, 또는 daemon으로 알려져 있다.
링커와 로더
일반적으로 프로그램은 디스크에 이진 실행 파일로 존재한다. .out
,.exe
등
CPU에서 실행하려면 프로그램을 메모리로 가져와 프로세스 형태로 배치되어야 한다.
재배치 가능 오브젝트 파일
: 임의의 물리 메모리 위치에 적재되도록 설계된 오브젝트 파일. 소스파일이 컴파일 되면 재배치 가능 오브젝트 파일이 된다.링커
: 재배치 가능 오브젝트 파일을 하나의 이진 실행 파일로 결합한다. 다른 오브젝트 파일 또는 라이브러리가 포함될 수 있다.로더
: 이진 실행 파일을 메모리에 적재한다. 이 과정까지 지나면 CPU 코어에서 실행할 수 있는 상태가 된다.재배치
: 링크 및 로드와 관련된 활동이다. 프로그램 부분에 최종 주소를 할당하고 프로그램 코드와 데이터를 해당 주소와 일치하도록 조장하여 프로그램이 실행될 때 코드가 라이브러리 함수를 호출하고 변수에 접근할 수 있게 한다.
응용 프로그램이 운영체제마다 다른 이유
각 운영체제는 고유한 시스템 콜 집합을 제공한다.
시스템 콜은 어느 정도 같더라도 다른 장벽으로 인해 응용 프로그램을 다른 운영체제에서 실행하기 어렵다.
응용 프로그램이 여러 운영체제에서 실행할 수 있게 하는 방법
- 응용 프로그램은 운영체제마다 인터프리터가 제공되는 인터프리터 언어로 작성될 수 있다. 단, 인터프리터는 기계어 코드로 구성된 프로그램에 비해 성능이 떨어지고, 각 운영체제 기능의 일부만 제공하므로 관련 응용 프로그램의 기능도 제한될 수 있다. (Python, Ruby)
- 응용 프로그램은 실행 중인 응용 프로그램을 포함하고 있는 가상 머신을 가진 언어로 작성될 수 있다. (Java) 인터프리터 시스템과 유사한 단점을 가진다.
- 응용 프로그램 개발자는 컴파일러가 기기 및 운영체제 고유의 이진 파일을 생성하는 표준 언어 또는 API 를 사용할 수 있다. 이식은 많은 시간이 소요될 수 있고, 많은 시험과 디버깅을 거쳐서 응용 프로그램의 새 버전마다 수행되어야 한다.
일반적으로 응용 프로그램의 이동성이 부족한 데에는 여러 원인이 있기 때문에 여전히 크로스 플랫폼 응용 프로그램을 개발하는 것이 어렵다.
그 추가 원인
- 각 운영체제는 헤더, 명령어 및 변수의 배치를 강제하는 응용 프로그램 이진 형식이 있다.
- CPU 는 다양한 명령어 집합을 가지며 해당 명령어가 포함된 응용 프로그램만 올바르게 실행할 수 있다.
- 운영체제는 응용 프로그램이 파일 생성과 네트워크 연결 열기와 같은 다양한 활동을 요청할 수 있는 시스템 콜을 제공한다.
ABI
아키텍처 수준에서 이진 코드의 여러 구성요소가 주어진 아키텍처에서 특정 운영체제와 상호 작용할 수 있는 방법을 정의하는 데 ABI (application binary interface) 가 사용된다.
주소 길이, 시스템 콜에 매개변수를 전달하는 방법, 런타임 스택 구성, 시스템 라이브러리의 이진 형식 및 데이터 유형의 크기 등 하위 수준의 세부 정보를 명시한다.
일반적으로 ABI 는 특정 아키텍처에 대해 명시되므로, 아키텍처 수준의 API 라고 할 수 있다.
특정 아키텍처에서 실행되는 특정 운영체제에 대해 ABI 가 정의되어 있기 때문에, 플랫폼 간 호환성을 거의 제공하지 않는다.
운영체제 설계 및 구현
설계 목표
시스템을 설계할 때의 첫 번째 문제점은 시스템의 목표와 명세를 정의하는 일이다.
요구 조건들은 근본적으로 '사용자 목적' 과 '시스템 목적' 으로 나눌 수 있다.
기법과 정책
한 가지 중요한 원칙은 기법으로부터 정책을 분리하는 것이다.
기법은 어떤 일을 '어떻게' 할 것인가를 결정하는 것이다.
정책은 '무엇을' 할 것인가를 결정하는 것이다.
정책과 기법의 분리는 융통성을 위해 아주 중요하다.
여러 정책에서 사용되기에 충분한 융통성 있는 일반적인 기법이 바람직한 경우가 많다.
구현
운영체제는 많은 사람에 의해 오랫동안 개발된 많은 프로그램의 집합체이므로, 구현 방법에 대해 일반적으로 말하는 것은 어렵다.
대부분의 운영체제는 C, C++ 같은 고급 언어로 작성되며, 극히 일부의 시스템만이 어셈블리 언어로 작성된다.
운영체제 구조
모놀리식 구조
운영체제를 구성하는 가장 간단한 구조는 구조가 아예 없는 것, 즉 커널의 모든 기능을 단일 주소 공간에서 실행되는 단일 정적 이진 파일에 넣는 것이다.
최초의 UNIX 운영체제는 커널과 시스템 프로그램 두 부분으로 구성된다.
커널은 여러 인터페이스와 장치 드라이버로 다시 분리된다.
커널은 시스템 콜을 통해 파일 시스템, CPU 스케줄링, 메모리 관리, 다른 운영체제 기능을 제공한다.
모놀리식 커널은 단순하지만, 구현과 확장이 어렵다.
그러나 성능 면에서는 뚜렷한 이점이 있다. 시스템 콜 인터페이스는 오버헤드가 거의 없고, 커널 안에서의 통신 속도가 빠르다.
계층적 접근
모놀리식 접근법은 시스템의 한 부분을 변경하면 다른 부분에 광범위한 영향을 줄 수 있으므로, 밀접하게 결합된 시스템이라고 불린다.
그 대안으로 느슨하게 결합된 시스템이 등장했다.
기능이 특정 기능 및 한정된 기능을 가진 개별적이고 작은 구성요소로 나뉜다.
한 구성요소의 변경이 해당 구성요소에만 영향을 미치고 다른 구성 요소에는 영향을 미치지 않아 시스템의 내부 작동을 더 자유롭게 생성하고 변경할 수 있다.
시스템은 다양한 방식으로 '모듈화' 될 수 있다.
그 중 계층적 접근 방식에서는 운영체제가 여러 layer로 나누어진다.
계층적 접근 방식의 장점은 구현과 디버깅이 간단하다는 것이다.
layer 들은 자신의 하위층들의 서비스와 기능만을 사용하도록 요구된다.
계층화된 시스템은 컴퓨터 네트워크 (TCP / IP) 및 웹 응용 프로그램에서 성공적으로 사용됐다.
그럼에도 순수한 계층 접근 방식을 사용하는 운영체제는 비교적 적다.
각 계층의 기능을 적절히 정의해야 하는 문제, 여러 계층을 통과하는 오버헤드로 인한 전반적인 성능 문제가 원인이다.
마이크로 커널
마이크로 커널 접근 방식은 모든 중요치 않은 구성 요소를 커널로부터 제거하고, 그들을 별도의 주소 공간에 존재하는 사용자 수준 프로그램으로 구현하여 운영체제를 구성하는 방법이다.
통상적으로 마이크로커널은 통신 설비 외에 추가적으로 최소한의 프로세스와 메모리 관리를 제공한다.
마이크로커널의 주 기능은 클라이언트 프로그램과 역시 사용자 공간에서 수행되는 다양한 서비스 간의 통신을 제공하는 것이다.
클라이언트 프로그램과 서비스는 결코 직접 상호작용하지 않고, 마이크로커널과 메시지를 교환함으로써 간접적으로 상호작용한다.
장점은 운영체제의 확장이 쉽다는 것이다. 모든 새로운 서비스는 사용자 공간에 추가되므로, 커널을 변경할 필요가 없다.
마이크로커널은 서비스 대부분이 커널이 아닌 사용자 프로세스로 수행되기 때문에 높은 보안성과 신뢰성을 제공한다. 한 서비스가 잘못되더라도, 운영체제의 다른 부분은 아무런 영향을 받지 않는다.
Ex. macOS 및 iOS 운영체제의 커널 구성요소인 Darwin
, QNX
그러나 가중된 시스템 기능 오버헤드 때문에 성능이 나빠진다. 두 개의 사용자 수준 서비스가 통신해야 하는 경우, 별도의 주소 공간에 서비스가 존재하기 때문에 메시지가 복사되어야 한다.
또한 메시지를 교환하기 위해 한 프로세스에서 다른 프로세스로 전환되어야 할 수도 있다.
모듈
적재가능 커널 모듈 (loadable kernel mkodules, LKM) 기법이 최근 기술 중 최선책이다. 커널은 핵심적인 구성요소의 집합을 가지고 있고, 부팅 때 혹은 실행 중에 부가적인 서비스들을 모듈을 통해 링크할 수 있다. 이런 유형의 설계가 현대 UNIX를 구현하는 일반적인 추세이다.
설계의 주안점은 커널은 핵심 서비스를 제공하고, 다른 서비스들은 커널이 실행되는 동안 동적으로 구현하는 것이다.
새로운 기능을 직접 커널에 추가하는 방식은 수정 사항이 생길 때마다 커널을 다시 컴파일 해야 한다는 단점이 있다.
커널의 각 부분이 정의되고 보호된 인터페이스를 가진다는 점에서는 계층 구조를 닮았지만, 모듈에서 임의의 다른 모듈을 호출할 수 있다는 점에서 계층 구조보다 유연하다. 중심 모듈은 단지 핵심 기능만 가지고 있고 다른 모듈의 적재 방법과 모듈들과 어떻게 통신하는지 안다는 점에서는 마이크로 커널과 유사하나, 통신을 위해 메시지 전달을 호출할 필요가 없기 때문에 더 효율적이다.
하이브리드 시스템
엄격하게 정의된 하나의 구조를 채택한 운영체제는 거의 존재하지 않는다. 보통 다양한 구조를 결합하여 성능, 보안 및 편리성 문제를 해결하려는 혼용 구조로 구성된다.